package com.gitee.wsl.mathematics.geometry.shape

import androidx.compose.ui.geometry.Rect
import com.gitee.wsl.ext.math.PI_RAD
import com.gitee.wsl.ext.math.piRad
import com.gitee.wsl.ext.number.half
import com.gitee.wsl.math.degreesToRadians
import com.gitee.wsl.unit.angle.AngleUnit
import com.gitee.wsl.unit.angle.ext.cos
import com.gitee.wsl.unit.angle.ext.sin
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sin


open class Rectangle(
    open var left: Float = 0f,
    open var top: Float = 0f,
    open var right: Float = 0f,
    open var bottom: Float = 0f
)  {
   constructor(rect: Rect):this(rect.left,rect.top,rect.right,rect.bottom)

     open var width: Float
        get() = abs(right-left)
        set(value) {
            right = left + value
        }

     open var height: Float
        get() = abs(bottom-top)
        set(value) {
            bottom = top + value
        }

    open val radius: Float
        get() {
            require(width == height) { "RectF must be a square." }
            return width.half
        }

    val center: Point
        get() = Point(centerX, centerY)

    val centerX:Float
        get() = left + width.half

    val centerY:Float
        get() = top + height.half

    val topLeft: Point
        get() = Point(top, left)

    val topRight: Point
        get() = Point(top, right)

    val bottomLeft: Point
        get() = Point(bottom, left)

    val bottomRight: Point
        get() = Point(bottom, right)

    fun set(rect: Rectangle) {
        left = rect.left
        top = rect.top
        right = rect.right
        bottom = rect.bottom
    }

    operator fun set(left: Float , top: Float , right:Float, bottom:Float) {
        this.left = left
        this.top = top
        this.right = right
        this.bottom = bottom
    }

    fun setHV(
        x: Float = 0f,
        y: Float = 0f,
        width: Float = Float.POSITIVE_INFINITY,
        height: Float = Float.POSITIVE_INFINITY
    ) {
        this.left = x
        this.top = y
        this.width = width
        this.height = height
    }

    fun setHV(x: Int, y: Int, width: Int, height: Int) {
        this.left = x.toFloat()
        this.top = y.toFloat()
        this.width = width.toFloat()
        this.height = height.toFloat()
    }

    fun reset(){
        setEmpty()
    }

    /**
     * Returns true if the viewport is empty `left >= right or bottom >= top`
     */
    val isEmpty: Boolean
        get() = left >= right || top >= bottom

    /**
     * Set the viewport to (0,0,0,0)
     */
    fun setEmpty() {
        bottom = 0f
        top = 0f
        right = 0f
        left = 0f
    }

    /**
     * Returns true iff the this and the other rectangle intersect.
     *
     * @param other The second rectangle being tested for intersection
     * @return true iff the two rectangles intersect.
     */
    fun intersects(other: Rectangle): Boolean {
        return this.left < other.right && other.left < this.right && this.top < other.bottom && other.top < this.bottom
    }

    /**
     * Offset the viewport by adding dx to its left and right coordinates, and adding dy to its top and bottom
     * coordinates.
     *
     * @param dx The amount to add to the viewport's left and right coordinates
     * @param dy The amount to add to the viewport's top and bottom coordinates
     */
    fun offset(dx: Float, dy: Float) {
        left += dx
        top += dy
        right += dx
        bottom += dy
    }

    /**
     * Offset the viewport to a specific (left, top) position, keeping its width and height the same.
     *
     * @param newLeft The new "left" coordinate for the viewport
     * @param newTop  The new "top" coordinate for the viewport
     */
    fun offsetTo(newLeft: Float, newTop: Float) {
        right += newLeft - left
        bottom += newTop - top
        left = newLeft
        top = newTop
    }

    /**
     * Inset the viewport by (dx,dy). If dx is positive, then the sides are moved inwards, making the viewport narrower.
     * If dx is negative, then the sides are moved outwards, making the viewport wider. The same holds true for dy and
     * the top and bottom.
     *
     * @param dx The amount to add(subtract) from the viewport's left(right)
     * @param dy The amount to add(subtract) from the viewport's top(bottom)
     */
    fun inset(dx: Float, dy: Float) {
        left += dx
        top -= dy
        right -= dx
        bottom += dy
    }


    fun padding(leftPadding: Float=0f, topPadding: Float=0f, rightPadding: Float=0f, bottomPadding: Float=0f){
        this.left += leftPadding
        this.top += topPadding
        this.right -= rightPadding
        this.bottom -= bottomPadding
    }

    /**
     * Returns true if (x,y) is inside the viewport. The left and top are considered to be inside, while the right and
     * bottom are not. This means that for a x,y to be contained: `left <= x < right and bottom <= y < top`. An
     * empty viewport never contains any point.
     *
     * @param x The X coordinate of the point being tested for containment
     * @param y The Y coordinate of the point being tested for containment
     * @return true iff (x,y) are contained by the viewport, where containment means `left <= x < right and top <=
     * y < bottom`
     */
     fun contains(x: Float, y: Float): Boolean {
        return !isEmpty // check for empty first
                && x >= left && x < right && y >= top && y < bottom
    }

    /**
     * Returns true iff the 4 specified sides of a viewport are inside or equal to this viewport. i.e. is this viewport
     * a superset of the specified viewport. An empty viewport never contains another viewport.
     *
     * @param left   The left side of the viewport being tested for containment
     * @param top    The top of the viewport being tested for containment
     * @param right  The right side of the viewport being tested for containment
     * @param bottom The bottom of the viewport being tested for containment
     * @return true iff the the 4 specified sides of a viewport are inside or equal to this viewport
     */
    fun contains(left: Float, top: Float, right: Float, bottom: Float): Boolean {
        // check for empty first
        return !isEmpty // now check for containment
                && this.left <= left && this.top <= top && this.right >= right && this.bottom >= bottom
    }

    /**
     * Returns true iff the specified viewport r is inside or equal to this viewport. An empty viewport never contains
     * another viewport.
     *
     * @param v The viewport being tested for containment.
     * @return true iff the specified viewport r is inside or equal to this viewport
     */
    operator fun contains(v: Rectangle): Boolean {
        // check for empty first
        return contains(v.left, v.top,  v.right, v.bottom)
    }

    /**
     * Update this Viewport to enclose itself and the specified viewport. If the specified viewport is empty, nothing is
     * done. If this viewport is empty it is set to the specified viewport.
     *
     * @param left   The left edge being unioned with this viewport
     * @param top    The top edge being unioned with this viewport
     * @param right  The right edge being unioned with this viewport
     * @param bottom The bottom edge being unioned with this viewport
     */
    fun union(left: Float, top: Float, right: Float, bottom: Float) {
        if (!isEmpty) {
            if (this.left < this.right && this.top < this.bottom) {
                if (this.left > left) this.left = left
                if (this.top > top) this.top = top
                if (this.right < right) this.right = right
                if (this.bottom < bottom) this.bottom = bottom
            } else {
                this.left = left
                this.top = top
                this.right = right
                this.bottom = bottom
            }
        }
    }

    /**
     * Update this Viewport to enclose itself and the specified viewport. If the specified viewport is empty, nothing is
     * done. If this viewport is empty it is set to the specified viewport.
     *
     * @param v The viewport being unioned with this viewport
     */
    fun union(v: Rectangle) {
        union(v.left, v.top, v.right, v.bottom)
    }

    /**
     * If the viewport specified by left,top,right,bottom intersects this viewport, return true and set this viewport to
     * that intersection, otherwise return false and do not change this viewport. No check is performed to see if either
     * viewport is empty. Note: To just test for intersection, use intersects()
     *
     * @param left   The left side of the viewport being intersected with this viewport
     * @param top    The top of the viewport being intersected with this viewport
     * @param right  The right side of the viewport being intersected with this viewport.
     * @param bottom The bottom of the viewport being intersected with this viewport.
     * @return true if the specified viewport and this viewport intersect (and this viewport is then set to that
     * intersection) else return false and do not change this viewport.
     */
    fun intersect(left: Float, top: Float, right: Float, bottom: Float): Boolean {
        if (this.left < right && left < this.right && this.bottom < top && bottom < this.top) {
            if (this.left < left) {
                this.left = left
            }
            if (this.top < top) {
                this.top = top
            }
            if (this.right > right) {
                this.right = right
            }
            if (this.bottom > bottom) {
                this.bottom = bottom
            }
            return true
        }
        return false
    }

    /**
     * If the specified viewport intersects this viewport, return true and set this viewport to that intersection,
     * otherwise return false and do not change this viewport. No check is performed to see if either viewport is empty.
     * To just test for intersection, use intersects()
     *
     * @param v The viewport being intersected with this viewport.
     * @return true if the specified viewport and this viewport intersect (and this viewport is then set to that
     * intersection) else return false and do not change this viewport.
     */
    fun intersect(v: Rectangle): Boolean {
        return intersect(v.left, v.top, v.right, v.bottom)
    }

    override fun toString(): String {
        return "Viewport [left=$left, top=$top, right=$right, bottom=$bottom]"
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is Rectangle) return false

        if (left != other.left) return false
        if (top != other.top) return false
        if (right != other.right) return false
        if (bottom != other.bottom) return false

        return true
    }

    override fun hashCode(): Int {
        var result = left.hashCode()
        result = 31 * result + top.hashCode()
        result = 31 * result + right.hashCode()
        result = 31 * result + bottom.hashCode()
        return result
    }
}

fun Rectangle.toRect() = Rect(left,top,right,bottom)

/**
 * Return the "bounding box" of a Rect that has a rotation of rotation radians at the (0,0) coordinates.
 */
fun Rectangle.boundsWith(rotation: AngleUnit): Rectangle {
    if (abs(rotation.normalized.radian) < 1e-3) return this

    val cos = rotation.cos()
    val sin = rotation.sin()

    val rxcos = top * cos
    val rxsin = top * sin
    val rwcos = width * cos
    val rwsin = width * sin
    val rysin = left * sin
    val rycos = left * cos
    val rhcos = height * cos
    val rhsin = height * sin

    // compute point ABCD of the rotated rectangle
    val a = Point(rxcos - rysin, rxsin + rycos)
    val b = Point(a.x + rwcos, a.y + rwsin)
    val c = Point(b.x - rhsin, b.y + rhcos)
    val d = Point(a.x - rhsin, a.y + rhcos)

    val minX = min(a.x, min(b.x, min(c.x, d.x)))
    val minY = min(a.y, min(b.y, min(c.y, d.y)))
    val maxX = max(a.x, max(b.x, max(c.x, d.x)))
    val maxY = max(a.y, max(b.y, max(c.y, d.y)))

    return Rectangle(minX, minY, maxX, maxY)
}

fun Rectangle.rotate(degrees: Float): Rectangle {
    when {
        degrees % PI_RAD == 0f -> Unit
        degrees % 0.5f.piRad == 0f -> {
            if (width != height) {
                set(
                    left = centerX - height.half,
                    top = centerY - width.half,
                    right = centerX + height.half,
                    bottom = centerY + width.half,
                )
            }
        }

        else -> {
            val alpha = degrees.degreesToRadians()
            val sinAlpha = sin(alpha)
            val cosAlpha = cos(alpha)

            val newWidth = abs(width * cosAlpha) + abs(height * sinAlpha)
            val newHeight = abs(width * sinAlpha) + abs(height * cosAlpha)

            set(
                left = centerX - newWidth.half,
                top = centerY - newHeight.half,
                right = centerX + newWidth.half,
                bottom = centerY + newHeight.half,
            )
        }
    }

    return this
}

fun Rectangle.updateIfExceeds(
    point: Point
) = updateIfExceeds(point.x,point.y)

fun Rectangle.updateIfExceeds(
    x: Float,
    y: Float,
) {
    set(
        left = left.coerceAtMost(x),
        top = top.coerceAtMost(y),
        right = right.coerceAtLeast(x),
        bottom = bottom.coerceAtLeast(y),
    )
}

operator fun Rectangle.component1(): Float = left

operator fun Rectangle.component2(): Float = top

operator fun Rectangle.component3(): Float = right

operator fun Rectangle.component4(): Float = bottom


fun Rectangle.overlap(r: Rectangle):Boolean = left < r.right && right > r.left && top < r.bottom && bottom > r.top

fun Iterable<Rectangle>.bounds(): Rectangle {
    var first = true
    var left = 0.0f
    var right = 0.0f
    var top = 0.0f
    var bottom = 0.0f
    for (r in this) {
        if (first) {
            left = r.left
            right = r.right
            top = r.top
            bottom = r.bottom
            first = false
        } else {
            left = min(left, r.left)
            right = max(right, r.right)
            top = min(top, r.top)
            bottom = max(bottom, r.bottom)
        }
    }
    return Rectangle(left, top, right, bottom)
}

/**
 * Circle that touches or contains all the corners ([Rectangle.topLeft], [Rectangle.topRight], [Rectangle.bottomLeft], [Rectangle.bottomRight]) of the rectangle.
 */
fun Rectangle.outerCircle(): Circle {
    return Circle(center, center.distance(topRight))
}

operator fun Rectangle.plus(broderWidth: Float): Rectangle {
    return Rectangle(
        this.left - broderWidth,
        this.top - broderWidth,
        this.right + broderWidth,
        this.bottom + broderWidth)
}